<!--
 * @Description  : 
 * @Version      : 1.0
 * @Author       : seraph
 * @Date         : 2024-12-26 12:19:10
 * @LastEditors  : seraph
 * @LastEditTime : 2025-01-20 11:33:28
-->
<template>
  <h3>额外的 ref 解包细节</h3>
  <hr />
  <h4>作为 reactive 对象的属性</h4>
  <p>
    一个 ref 会在作为响应式对象的属性被访问或修改时自动解包。换句话说，它的行为就像一个普通的属性：
  </p>
  <pre><code class="javascript" v-html="highlighted1"></code></pre>
  <p>修改 上述三个变量中的任意一个，都会触发响应式系统的更新。</p>
  <p>
    count is: {{ count }}, state.count is: {{ state.count }}, varA is: {{ varA }}, otherCount is:
    {{ otherCount }}
  </p>
  <p>
    <button @click="countAdd">count++</button>
    <button @click="stateCountAdd">stateCount++</button>
    <button @click="varAAdd">varA++</button>
    <button @click="otherCount++">otherCount++</button>
    <button @click="changeStateCount">changeStateCount</button>
  </p>
  <p>如果将一个新的 ref 赋值给一个关联了已有 ref 的属性，那么它会替换掉旧的 ref：</p>
  <pre><code class="javascript" v-html="highlighted2"></code></pre>
  <p class="bolder">
    当一个 ref 被嵌套在一个使用 reactive 创建的深层响应式对象中时，在模板或响应式计算属性中访问该
    ref，会自动解包。这意味着可以直接使用 ref 的值，而无需使用 .value 访问。
  </p>
  <p class="bolder">
    当 ref 作为浅层响应式对象（例如使用 shallowReactive
    创建的对象）的属性被访问时，不会自动解包，必须使用 .value 来访问 ref 的内部值。
  </p>
  <pre><code class="javascript" v-html="highlighted3"></code></pre>
  <hr />
  <!-- NOTE: 数组和集合的注意事项 -------------------------------- -->
  <h4>数组和集合的注意事项​</h4>
  <p>
    与 reactive 对象不同的是，当 ref 作为<span class="bolder"> 响应式数组 </span>或<span
      class="bolder"
    >
      原生集合集合
    </span>
    (如 Map) 中的元素被访问时，它不会被解包：
  </p>
  <pre><code class="javascript" v-html="highlighted4"></code></pre>
  <hr />
  <!-- NOTE: 总结 ---------------------------------------------- -->
  <h4 class="red">总结</h4>
  <p>
    reactive() 与 ref() 都可以用于声明响应式状态，但 reactive()
    具有更高级的功能，可以声明复杂的响应式对象，并且可以自动解包 ref。
  </p>
  <p>
    1、当使用 reactive 创建深层响应式对象时，嵌套在其中的 ref
    会自动解包，使代码更加简洁，避免了频繁使用 .value。 2、而当使用 shallowReactive
    创建浅层响应式对象时，嵌套在其中的 ref 不会自动解包，需要使用 .value 来访问和修改其内部值。
    3、这种设计有助于在不同的响应式对象深度下灵活使用 ref，根据需要选择是否要利用自动解包的特性。
  </p>
  <hr />
  <!-- NOTE: 在模板中解包的注意事项 ---------------------------- -->
  <h4>在模板中解包的注意事项</h4>
  <p>
    在模板渲染上下文中，只有顶级的 ref 属性才会被解包。 在下面的例子中，count 和 object
    是顶级属性，但 object.id 不是：
  </p>
  <pre><code class="javascript" v-html="highlighted5"></code></pre>
  <p>因此，这个表达式按预期工作：</p>
  <pre><code class="html" v-html="highlighted6"></code></pre>
  <pre><code class="html" v-html="highlighted7"></code></pre>
  <p>
    渲染的结果将是 [object Object]1，因为在计算表达式时 object.id 没有被解包，仍然是一个 ref
    对象。为了解决这个问题，我们可以将 id 解构为一个顶级属性：
  </p>
  <pre><code class="javascript" v-html="highlighted8"></code></pre>
  <pre><code class="html" v-html="highlighted9"></code></pre>
  <p>渲染的结果将是 2。</p>
  <p>
    另一个需要注意的点是，如果 ref 是文本插值的最终计算值 (即 {{}}
    标签)，那么它将被解包，因此以下内容将渲染为 1：
  </p>
  <pre><code class="html" v-html="highlighted10"></code></pre>
  <p>该特性仅仅是文本插值的一个便利特性，等价于：</p>
  <pre><code class="html" v-html="highlighted11"></code></pre>
</template>

<script lang="ts" setup>
import { ref, reactive } from 'vue'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
import javascript from 'highlight.js/lib/languages/javascript'
import html from 'highlight.js/lib/languages/xml'

hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('html', html)

const rawCode1 = `const count = ref(0)
const state = reactive({ count })
const varA = count;`

const highlighted1 = ref(
  hljs
    .highlight(rawCode1, { language: 'javascript' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode2 = `const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1</pre>`

const highlighted2 = ref(
  hljs
    .highlight(rawCode2, { language: 'javascript' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode3 = `const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)`

const highlighted3 = ref(
  hljs
    .highlight(rawCode3, { language: 'javascript' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode4 = `const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)`

const highlighted4 = ref(
  hljs
    .highlight(rawCode4, { language: 'javascript' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode5 = `const count = ref(0)
const object = { id: ref(1) }`

const highlighted5 = ref(
  hljs
    .highlight(rawCode5, { language: 'javascript' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode6 = `{{ count + 1 }}`

const highlighted6 = ref(
  hljs
    .highlight(rawCode6, { language: 'html' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode7 = `{{ object.id + 1 }}`

const highlighted7 = ref(
  hljs
    .highlight(rawCode7, { language: 'html' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode8 = `const { id } = object`

const highlighted8 = ref(
  hljs
    .highlight(rawCode8, { language: 'javascript' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode9 = `{{ id + 1 }}`

const highlighted9 = ref(
  hljs
    .highlight(rawCode9, { language: 'html' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode10 = `{{ object.id + 1 }}`

const highlighted10 = ref(
  hljs
    .highlight(rawCode10, { language: 'html' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const rawCode11 = `{{ object.id.value + 1 }}`

const highlighted11 = ref(
  hljs
    .highlight(rawCode11, { language: 'html' })
    .value.replace(/&amp;gt;/g, '&gt;')
    .replace(/&amp;lt;/g, '&lt;'),
)

const count = ref(0)
const state = reactive({ count })
const varA = count
const otherCount = ref(2)

const countAdd = () => {
  count.value++
}

const stateCountAdd = () => {
  state.count++
}

const varAAdd = () => {
  varA.value++
  console.log(Date.now().toString().slice(-3) + ': varA is: ' + varA.value)
}

const changeStateCount = () => {
  state.count = otherCount
}
</script>

<style scoped>
p {
  font-size: 16px;
  padding: 2px;
  margin: 5px 0;
}

/* 设置预格式化文本的样式 */
pre {
  /* 背景色 */
  background-color: #f1ffed;
  /* 边框 */
  border: 1px solid #ccc;
  /* 缩小内边距 */
  padding: 5px 5px 5px 10px;
  /* 允许滚动 */
  overflow: auto;
  /* 可选：设置上下外边距 */
  margin: 0px 0;
  /* 允许换行 */
  /* normal: 连续的空白字符会被合并为一个空格。文本会在允许的断点处自动换行。
     nowrap：连续的空白字符会被合并为一个空格。文本不会自动换行，即使在允许的断点处也不会换行。
     pre：保留所有的空白字符（包括空格、制表符和换行符）。文本只会在遇到换行符或者 <br> 标签时换行。
     pre-wrap：保留所有的空白字符。文本会在遇到换行符、<br> 标签，或者达到容器边界时换行。
     pre-line：合并连续的空白字符为一个空格，但保留换行符。文本会在遇到换行符、<br> 标签，或者达到容器边界时换行。
   */
  white-space: pre-wrap;
  /* 行高 */
  line-height: 120%;
  /* 定义缩进 */
  tab-size: 4;
}

h3,
h4,
h5,
h6 {
  margin: 25px 0 10px 0;
  line-height: 100%;
}

.bolder {
  font-weight: bolder;
}

.red {
  color: red;
}

button {
  margin: 5px 5px 5px 10px;
  line-height: 150%;
  width: auto;
  padding: 0 10px;
}
</style>
